﻿/**
 * Peter
 * Created by: Peter Development Team
 *    http://peter.codeplex.com/
 * 
 * GNU General Public License version 2 (GPLv2)
 *    http://peter.codeplex.com/license
 *
 *  This code is provided on an AS IS basis, with no WARRANTIES,
 *  CONDITIONS or GUARANTEES of any kind.
 *
 **/

using System.Windows;
using System.Windows.Media;
using ICSharpCode.AvalonEdit;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Rendering;

namespace Peter.Logic.AvalonEdit
{
   /// <summary>
   /// A background renderer for highlighting matching elements.
   /// </summary>
   public class HighlightBracesBackgroundRenderer : IBackgroundRenderer
   {
      private readonly TextEditor m_Editor;

      /// <summary>
      /// Initializes a new Highlight Current Line Background Renderer
      /// </summary>
      /// <param name="editor">The editor to attach to.</param>
      public HighlightBracesBackgroundRenderer (TextEditor editor)
      {
         this.m_Editor = editor;
         this.m_Editor.TextArea.TextView.BackgroundRenderers.Add (this);
      }

      /// <summary>
      /// Gets the layer to draw this on.
      /// </summary>
      public KnownLayer Layer
      {
         get { return KnownLayer.Selection; }
      }

      /// <summary>
      /// Removes this background renderer from the stack to renderers.
      /// </summary>
      public void Remove ()
      {
         if (this.m_Editor.TextArea.TextView.BackgroundRenderers.Contains (this))
            this.m_Editor.TextArea.TextView.BackgroundRenderers.Remove (this);
      }

      /// <summary>
      /// Draws the line highlight
      /// </summary>
      /// <param name="textView">The text view with the line hightlighted.</param>
      /// <param name="drawingContext"></param>
      public void Draw (TextView textView, DrawingContext drawingContext)
      {
         textView.EnsureVisualLines ();
         var offset = this.m_Editor.CaretOffset;
         var textLength = this.m_Editor.Text.Length;
         if (offset >= textLength) return;
         var currChar = this.m_Editor.Text[offset];
         var prevChar = offset > 0 ? this.m_Editor.Text[offset - 1] : ' ';
         var highlightStartOffset = -1;

         if (currChar == '(' || currChar == '{' || currChar == '[' || currChar == '<')
            highlightStartOffset = offset;
         else if (prevChar == '(' || prevChar == '{' || prevChar == '[' || prevChar == '<')
         {
            currChar = prevChar;
            highlightStartOffset = offset - 1;
         }

         if (highlightStartOffset != -1)
         {
            var lookFor = ' ';
            if (currChar == '(')
               lookFor = ')';
            else if (currChar == '{')
               lookFor = '}';
            else if (currChar == '[')
               lookFor = ']';
            else if (currChar == '<')
               lookFor = '>';

            var inners = 0;
            var highlightEndOffset = highlightStartOffset + 1;
            HighlightChar (textView, drawingContext, highlightStartOffset);
            while (highlightEndOffset < textLength)
            {
               var testChar = this.m_Editor.Text [highlightEndOffset];
               if (testChar == lookFor)
               {
                  if (inners == 0)
                  {
                     HighlightChar(textView, drawingContext, highlightEndOffset);
                     break;
                  }
                  inners--;
               }
               else if (testChar == currChar)
                  inners++;
               highlightEndOffset++;
            }
         }
         else
         {
            currChar = this.m_Editor.Text[offset];
            prevChar = offset > 0 ? this.m_Editor.Text[offset - 1] : ' ';
            highlightStartOffset = -1;

            if (currChar == ')' || currChar == '}' || currChar == ']' || currChar == '>')
               highlightStartOffset = offset;
            else if (prevChar == ')' || prevChar == '}' || prevChar == ']' || prevChar == '>')
            {
               currChar = prevChar;
               highlightStartOffset = offset - 1;
            }

            if (highlightStartOffset != -1)
            {
               var lookFor = ' ';
               if (currChar == ')')
                  lookFor = '(';
               else if (currChar == '}')
                  lookFor = '{';
               else if (currChar == ']')
                  lookFor = '[';
               else if (currChar == '>')
                  lookFor = '<';

               var inners = 0;
               var highlightEndOffset = highlightStartOffset - 1;
               HighlightChar (textView, drawingContext, highlightStartOffset);
               while (highlightEndOffset >= 0)
               {
                  var testChar = this.m_Editor.Text [highlightEndOffset];
                  if (testChar == lookFor)
                  {
                     if (inners == 0)
                     {
                        HighlightChar (textView, drawingContext, highlightEndOffset);
                        break;
                     }
                     inners--;
                  }
                  else if (testChar == currChar)
                     inners++;
                  highlightEndOffset--;
               }
            }
         }
      }

      /// <summary>
      /// Highlights the character at the given offset.
      /// </summary>
      /// <param name="textView">Text view to highlight character at.</param>
      /// <param name="drawingContext">Drawing context used to highlight.</param>
      /// <param name="offset">Offset at which to highlight.</param>
      private static void HighlightChar (TextView textView, DrawingContext drawingContext, int offset)
      {
         var segment = new TextSegment {StartOffset = offset, EndOffset = offset + 1};
         foreach (var r in BackgroundGeometryBuilder.GetRectsForSegment (textView, segment))
            drawingContext.DrawRectangle (Brushes.LightSkyBlue, new Pen (Brushes.LightGray, 0), new Rect (r.TopLeft, r.Size));
      }
   }
}
